/*
 ============================================================================
 Name        : code-cleaner.c
 Author      : Shaohong
 Version     : see http://code.google.com/p/shaohong-aalto-study/ revision logs for details
 Description   : Shaohong's assignment for Unix Application programming
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>


#include "logger.h"
#include "parsingUtils.h"

static const char * MODULE_NAME = "main";
static const char * SIG_HANDLER = "SIG_HANDLER";

// number of child processes
int childProcessNO = 0;

// cache for all the childProcess IDs
static pid_t * childProcessIDs;


void printUsage(const char *EXE_NAME);

void waitForChildren();

void printChildExitStatus(pid_t childpid, int status);

//The SIG handler for the child process
void childSigHandler(int sigNo) {
	fprintf(stderr, "PID %ld received signal %d !!\n", (long) getpid(), sigNo);
	logDebugVarg(SIG_HANDLER, "signal %d received!", sigNo);

	cancelCleaning();
}

/*
 *The SIG_INT handler for the main process.
 *Main process will send SIGUSR1 to all child process.
 */
void mainSigIntHandler(int sigNo) {

	fprintf(stderr, "PID %ld received SIGINT!!!\n", (long) getpid());
	logDebugVarg(SIG_HANDLER, "signal %d received!", sigNo);

	for (int i = 0; i < childProcessNO; i++) {

		// send SIGUSR1 to all child processes.
		fprintf(stderr, "sending SIGUSR1 to childProcess PID %ld\n",
				(long) childProcessIDs[i]);

		kill(childProcessIDs[i], SIGUSR1);
	}

	waitForChildren();

	exit(EXIT_FAILURE);
}

/*
 * helper function to install signal handler for the given signal
 */
void setupSigHandler(int sigNo, void(*sigHandler)(int)) {
	struct sigaction sigIntAction;

	//we won't block any during our execution
	sigemptyset(&sigIntAction.sa_mask);

	//no special options
	sigIntAction.sa_flags = 0;

	//set the SIGINT handler
	sigIntAction.sa_handler = sigHandler;

	assert((sigaction(sigNo, &sigIntAction, NULL)) == 0);
}

int main(int argc, char ** argv) {
	if (argc == 1) {
		printUsage(argv[0]);
		return EXIT_FAILURE;
	}

	childProcessNO = argc - 1;
	childProcessIDs = calloc(childProcessNO, sizeof(pid_t));

	/*get the list of input file names
	 argv[0] should be ignored*/
	char ** input_files = &argv[1];

	pid_t childpid;
	for (int i = 0; i < childProcessNO; i++) {

		childpid = fork();

		if (childpid == 0) {
			// child will ignore all SIGINT;
			setupSigHandler(SIGINT, SIG_IGN);

			// child will take care of SIGUSR1
			setupSigHandler(SIGUSR1, childSigHandler);

			//we're inside the child process
			//child process just clean the given file and return
			logDebugVarg(MODULE_NAME, "PID: %ld cleaning file \"%s\"",
					(long) getpid(), input_files[i]);
			if (EXIT_SUCCESS == cleanSourceCode(input_files[i])) {

				fprintf(stderr,
						"Child with PID: %ld finished cleaning file \"%s\"!\n",
						(long) getpid(), input_files[i]);

				return EXIT_SUCCESS;
			} else {
				fprintf(stderr,
						"Child with PID: %ld failed to clean file \"%s\":(\n",
						(long) getpid(), input_files[i]);
				return EXIT_FAILURE;
			}

		}

		//save the child pid
		childProcessIDs[i] = childpid;

		logDebugVarg(MODULE_NAME,
				"created new process PID: %ld to clean \"%s\"", (long) childpid,
				input_files[i]);
		fprintf(stderr, "created new process PID: %ld to clean \"%s\"\n",
				(long) childpid, input_files[i]);
	}

	// sets up the SIGINT handler for the main process
	setupSigHandler(SIGINT, mainSigIntHandler);

	waitForChildren();

	return EXIT_SUCCESS;
}


/*
 * wait for all the child processes to exit
 */
void waitForChildren() {

	const char * msg = "waiting for child processes to exit!";
	fprintf(stderr, "%s\n", msg);
	logDebug(MODULE_NAME, msg);

	// main process will wait for all child to exit and then exit
	for (int i = 0; i < childProcessNO; i++) {
		pid_t childpid;
		int status;

		childpid = wait(&status);

		if (childpid != -1) {
			printChildExitStatus(childpid, status);
		} else {
			// wait returned -1, let's check the error no
			if (ECHILD == errno) {
				const char * msg = "All child processes have exited!";
				logDebug(MODULE_NAME, msg);
				fprintf(stderr, "%s\n", msg);
				break;
			}
		}
	}

	fprintf(
			stderr,
			"All cleaning processes exited! Please see log file for details!\n");

	free(childProcessIDs);

}

/*
 * print out the usage of this program
 */
void printUsage(const char *EXE_NAME) {
	fprintf(stderr, "Usage: %s input_file...\n", EXE_NAME);
	fprintf(stderr, "Where the input_file(s) are c/c++ source files.\n");
	fprintf(stderr, "%s removes the comments and empty lines from input_file\n"
			"and save the results in input_file.clean.\n", EXE_NAME);
}

/*
 * print the exit status of the child processes
 */
void printChildExitStatus(pid_t childpid, int status) {

	if (WIFEXITED(status)) {
		if (!WEXITSTATUS(status)) {
			fprintf(stderr, "Child process with PID %ld terminated normally\n", (long) childpid);

			logDebugVarg(
					MODULE_NAME,
					"Child process with PID %ld successfully finished its code cleaning job!",
					(long) childpid);
		} else {
			fprintf(stderr, "Child process with PID %ld terminated with status %d\n",
					(long) childpid, WEXITSTATUS(status));

			logDebugVarg(MODULE_NAME, "Child process with PID %ld terminated with status %d",
					(long) childpid, WEXITSTATUS(status));
		}

	}

	if (WIFSIGNALED(status))
		fprintf(stderr, "Child %ld terminated due to uncaught signal %d\n",
				(long) childpid, WTERMSIG(status));
	if (WIFSTOPPED(status))
		fprintf(stderr, "Child %ld stopped due to signal %d\n", (long) childpid,
				WSTOPSIG(status));

}
